﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading.Tasks;
using Pittypat.Configuration;

namespace Pittypat.Data
{
    /// <summary>
    /// <see cref="IDataService"/> 的抽象实现。
    /// </summary>
    public abstract class DataService : IDataService
    {
        /// <summary>
        /// 初始化 <see cref="DataService"/> 类的新实例。
        /// </summary>
        protected DataService()
        {
        }

        /// <summary>
        /// 在当前数据服务上异步执行指定的功能数据操作。
        /// </summary>
        /// <typeparam name="TArgs">要执行的功能的参数实体类的类型。</typeparam>
        /// <param name="model">功能模型对象，其中包含了功能的参数列表（也就是功能实体类的一个对象实例）、执行结果、结果错误消息以及当前用户等。</param>
        /// <param name="progress">跟踪和汇报异步操作的进度。如果当前请求使用了 WebSocket，可以通过该对象汇报操作进度，以便向用户显示进度条。</param>
        /// <returns>异步操作任务。</returns>
        public abstract Task Exec<TArgs>(FunctionModel<TArgs> model, IAsyncProgress progress) where TArgs : FunctionArgs, new();

        #region 数据服务缓存

        // 缓存的数据服务字典，键为模块 Api 类的类型，值为数据服务的对象实例。使用缓存可以大大减少反射的次数，提高运行效率
        static readonly Dictionary<Type, IDataService> dataServices = new Dictionary<Type, IDataService>(128);

        // 同步对数据服务字典的访问
        static readonly object dataServicesLock = new object();

        /// <summary>
        /// 为指定类型的 <see cref="Module"/> 类型获取或创建一个新的数据服务对象，如果此前曾经创建过该类型的数据服务，则返回缓存的数据服务，否则创建新的对象实例并缓存。
        /// </summary>
        /// <param name="apiType">实现了 <see cref="Module"/> 抽象类型的 Api 类型。</param>
        /// <returns>可以用于在目标类型模块对象实例中进行数据操作的 <see cref="IDataService"/> 对象实例。</returns>
        /// <exception cref="ArgumentNullException">apiType 为 null。</exception>
        /// <exception cref="ArgumentException">apiType 不是从 <see cref="Module"/> 继承的类型，或者是一个抽象类型，或者其命名空间无法解析为 "开发者代码.App标识符.模块标识符"。</exception>
        /// <exception cref="ConfigurationErrorsException">没有找到相关的配置信息，或者配置信息无效。</exception>
        internal static IDataService Find(Type apiType)
        {
            if (apiType == null)
            {
                throw new ArgumentNullException("apiType");
            }

            if (!apiType.IsSubclassOf(typeof(Module)))
            {
                // 不是从 Module 继承
                throw new ArgumentException(apiType.FullName + " 不是一个模块类型。", "apiType");
            }

            if (apiType.IsAbstract)
            {
                // 是抽象类型
                throw new ArgumentException(apiType.FullName + " 是一个抽象类型。", "apiType");
            }

            IDataService dataService = null;

            // 首先尝试从缓存中获取
            lock (dataServicesLock)
            {
                if (dataServices.TryGetValue(apiType, out dataService))
                {
                    // 从缓存中找到了数据服务实例
                    return dataService;
                }
            }

            // 命名空间的格式总是被认为：开发者代码.App标识符.模块标识符
            var ns = apiType.Namespace;
            var names = ns.Split('.');

            if (names.Length < 3)
            {
                // 命名空间不是期待的格式
                throw new ArgumentException(apiType.FullName + " 的命名空间无法解析为 \"开发者代码.App标识符.模块标识符\" 的格式。", "apiType");
            }

            // 查找 App 配置信息
            var appConfig = PittypatConfig.Current.Apps[names[0], names[1]];

            if (appConfig == null)
            {
                throw new ConfigurationErrorsException(string.Format("没有找到 App '{0}.{1}' 的配置信息。", names[0], names[1]));
            }

            // 查找模块的配置信息
            var moduleConfig = appConfig.Modules[names[2]];

            if (moduleConfig == null)
            {
                throw new ConfigurationErrorsException(string.Format("没有找到模块 '{0}' 的配置信息。", ns));
            }

            if (string.IsNullOrEmpty(moduleConfig.DataServiceType))
            {
                // 没有为目标模块提供数据服务类型，也就是说，目标模块不需要访问数据服务
                throw new ConfigurationErrorsException(string.Format("没有提供模块 '{0}' 的数据服务类型。", ns));
            }

            if (string.IsNullOrEmpty(moduleConfig.DataServiceUrl))
            {
                // 没有为目标模块提供数据服务的路径，比如没有提供数据库连接字符串
                throw new ConfigurationErrorsException(string.Format("没有提供模块 '{0}' 的数据服务的路径。", ns));
            }

            if (moduleConfig.DataServiceType.Equals("MySql", StringComparison.OrdinalIgnoreCase))
            {
                // 预定义的 MySQL 数据库
                dataService = new MySqlService(DataServiceUrlDecryptor.Decrypt(moduleConfig.DataServiceUrl));
            }
            else if (moduleConfig.DataServiceType.Equals("SqlServer", StringComparison.OrdinalIgnoreCase))
            {
                // 预定义的 SQL SERVER 数据库
                dataService = new SqlServerService(DataServiceUrlDecryptor.Decrypt(moduleConfig.DataServiceUrl));
            }
            else
            {
                // 将数据服务类型字符串看作完整的类型名称，加载目标服务类型
                var dataServiceType = Type.GetType(moduleConfig.DataServiceType);
                if (dataServiceType == null)
                {
                    // 没有找到目标类型
                    throw new ConfigurationErrorsException(string.Format("没有提供找到数据服务类型 '{0}'。", moduleConfig.DataServiceType));
                }

                if (!typeof(IDataService).IsAssignableFrom(dataServiceType))
                {
                    // 目标类型没有实现 IDataService
                    throw new ConfigurationErrorsException(string.Format("没有数据服务 '{0}' 不是一个 IDataService。", moduleConfig.DataServiceType));
                }

                if (dataServiceType.IsAbstract || !dataServiceType.IsClass)
                {
                    // 目标类型是抽象类型，无法创建对象实例
                    throw new ConfigurationErrorsException(string.Format("没有数据服务 '{0}' 是一个抽象类型，或者不是一个类类型。", moduleConfig.DataServiceType));
                }

                // 解密数据服务路径，创建数据服务的对象实例
                dataService = (IDataService)Activator.CreateInstance(dataServiceType, DataServiceUrlDecryptor.Decrypt(moduleConfig.DataServiceUrl));
            }

            // 缓存数据服务对象
            lock (dataServicesLock)
            {
                // 再次检查此前是否已经缓存
                if (!dataServices.ContainsKey(apiType))
                {
                    dataServices.Add(apiType, dataService);
                }
            }

            return dataService;
        }

        #region 数据服务路径解密程序

        #region DefaultDecryptor

        /// <summary>
        /// 默认的服务路径解密程序实现。该实现认为服务路径没有被加密。
        /// </summary>
        sealed class DefaultDecryptor : IDataServiceUrlDecryptor
        {
            /// <summary>
            /// 默认的服务路径解密程序实例。
            /// </summary>
            internal static readonly DefaultDecryptor Default = new DefaultDecryptor();

            /// <summary>
            /// 初始化 <see cref="DefaultDecryptor"/> 类的新实例。
            /// </summary>
            private DefaultDecryptor()
            {
            }

            /// <summary>
            /// 解密指定的服务路径。
            /// </summary>
            /// <param name="url">要解密的服务路径。</param>
            /// <returns>原样返回 url 参数。</returns>
            string IDataServiceUrlDecryptor.Decrypt(string url)
            {
                return url;
            }
        }

        #endregion

        // 已经缓存的当前正在使用的服务路径解密程序实例。
        static IDataServiceUrlDecryptor dataServiceUrlDecryptor = null;

        // 同步对当前解密程序的访问。
        static readonly object dataServiceUrlDecryptorLock = new object();

        // 当前正在使用的服务路径解密程序。
        static IDataServiceUrlDecryptor DataServiceUrlDecryptor
        {
            get
            {
                if (dataServiceUrlDecryptor == null)
                {
                    lock (dataServiceUrlDecryptorLock)
                    {
                        if (dataServiceUrlDecryptor == null)
                        {
                            // 获取配置文件中配置的解密程序的类型名称。
                            var name = PittypatConfig.Current.DataServiceUrlDecryptor;

                            if (string.IsNullOrEmpty(name))
                            {
                                // 没有提供配置，使用默认的解密程序。
                                dataServiceUrlDecryptor = DefaultDecryptor.Default;
                            }
                            else
                            {
                                // 加载解密程序的类型
                                var type = Type.GetType(name);
                                if (type == null)
                                {
                                    throw new ConfigurationErrorsException(string.Format("没有找到服务路径解密程序：{0}。", name));
                                }

                                // 创建解密程序的对象实例
                                dataServiceUrlDecryptor = (IDataServiceUrlDecryptor)Activator.CreateInstance(type);
                            }
                        }
                    }
                }

                return dataServiceUrlDecryptor;
            }
        }

        #endregion

        #endregion
    }
}
